#include "../include/io.h"
#include "../include/string.h"
#include "../include/shell.h"
#include "../include/lsusb.h"
#include "../include/defs.h"
#include "../include/video.h"
#include "../include/keyboard.h"
#include "../include/time.h"
#include "../include/random.h"
#include "../include/video.h"
#include "../include/kasm.h"
#include "../include/shutdown.h"
#include "../include/proc.h"
#include "../include/sched.h"

extern int stdin;
extern int CurrentTTY;
extern int CurrentPID;


#define CMDBUFFER_SIZE  256
#define MAX_SHELLS      20
#define PROMPT_LEN      13
#define NOTFOUND        -1
#define EXIT            25
#define US_KEYB         0
#define ES_KEYB         1
#define CMD_LIST_LEN    26

/* Definicion de MACROS utiles */
#define ERASE_SPACES(c) while ( ( (c) = getchar() ) == ' ' || c == '\t')

/* Estructura que define un comando ejecutado por shell */

int promptlen = PROMPT_LEN;

typedef struct
{
    char *cmdname;
    cmdF cmd;
    char *cmdhelp;
    int len;
} cmdT;

static void clearBuffer(char * buffer, int bufsize);
funcId parseCommand(char * cmdbuffer, unsigned int length);


static int handlerNop(int ppid, int pid, const char * args);
static int handlerHelp(int ppid, int pid, const char * args);
static int handlerClear(int ppid, int pid, const char * args);
static int handlerKeybLayout(int ppid, int pid, const char * args);
static int handlerLsusb(int ppid, int pid, const char * args);
static int handlerLspci(int ppid, int pid, const char * args);
static int handlerDate(int ppid, int pid, const char * args);
static int handlerLs(int ppid, int pid, const char * args);
static int handlerCounter(int ppid, int pid, const char * args);
static int handlerKill(int ppid, int pid, const char * args);
static int handlerRandom(int ppid, int pid, const char * args);
static int handlerSkin(int ppid, int pid, const char * args);
static int handlerCiclo(int ppid, int pid, const char * args);
static int handler2Ciclo(int ppid, int pid, const char * args);
static int handlerHola(int ppid, int pid, const char * args);
static int handler2Hola(int ppid, int pid, const char * args);
static int handlerNice(int ppid, int pid, const char * args);
static int handlerFactOK(int ppid, int pid, const char * args);
static int handlerFactNotOK(int ppid, int pid, const char * args);
static int handlerTop(int ppid, int pid, const char * args);
static int handlerSched(int ppid, int pid, const char * args);
static int handlerShutdown(int ppid, int pid, const char * args);
static int handlerExit(int ppid, int pid, const char * args);
static int handlerRestart(int ppid, int pid, const char * args);

cmdT commands[CMD_LIST_LEN] =
{
    {"", handlerNop, "", 1},
    {"help", handlerHelp, "Usage: help \"command\", shows sintax and command description.", 5},
    {"clear", handlerClear, "Usage: clear, clears the screen", 6},
    {"set_keyblayout", handlerKeybLayout,
         "Usage: set_keyblayout \"english | spanish\". Sets the keyboard distribution", 15},
    {"lsusb", handlerLsusb, "Usage: lsusb, List all USB devices currently\
 plug in to the computer. Shows a description of each", 6},
    {"lspci", handlerLspci, "Usage: lspci, List all PCI devices\
 currently plug in to the computer. Shows a description of each", 6},
    {"date", handlerDate, "Usage: date, prints date in GMT0", 5},
    {"ls", handlerLs, "Usage: ls, lists commands", 3},
    {"counter", handlerCounter, "Usage: counter, counts a number", 8},
    {"kill", handlerKill, "Usage: kill pid, kills a pid", 5},
    {"random", handlerRandom, "Usage: random num, Gets a random number", 7},
    {"skin", handlerSkin, "Usage: skin matrix pinkdance default, Changes shell's skin", 5},
    {"sched", handlerSched, "Usage: sched \"priority | lottery\".", 6},
    {"nice", handlerNice, "Usage: nice pid. Priority goes from 0 to 4", 5},
    {"2hola", handler2Hola, "Usage: 2hola. Runs two process of hola", 6},
    {"hola", handlerHola, "Usage: hola. Prints hola 5000 times", 5},
    {"2ciclo", handler2Ciclo, "Usage: 2hola. Runs two process of ciclo", 7},
    {"ciclo", handlerCiclo, "Usage: hola. while(1) that not ends", 6},
    {"factOK", handlerFactOK, "Usage: get factorial of 10", 7},
    {"factNotOK", handlerFactNotOK, "Usage: get factorial of 10", 10},
    {"top", handlerTop, "Usage: top for process status", 4},
    {"shutdown", handlerShutdown, "Usage: shutdown. Turns off the PC", 9},
    {"tf", 0, "Usage: tf. Restarts the PC with a tripleFault", 3},
    {"restart", handlerRestart, "Usage: restart, reboots the computer", 8},
    {"exit", handlerExit, "Usage: exit, quits the current shell", 5}
};

int
shell(int argc, char ** argv)
{
    int exit = 0;
    funcId id = NOTFOUND;
    char cmdbuffer[CMDBUFFER_SIZE + 1] = {0};
//         TODO: Por ahora es cero
    int pid = (int)argv;

    /* Inicializa la pantalla de la shell en blanco,
    con el cursor al principio y con el prompt */
    clearScreen();

    while(!exit)
    {
        /* Limpia el buffer de comandos */
        clearBuffer(cmdbuffer, CMDBUFFER_SIZE);

        prompt(pid);

        id = getCommand(cmdbuffer, CMDBUFFER_SIZE);

        if (id != NOTFOUND && id != EXIT)
        {
            /* se manda cmdbuffer + commands[id].len para que reciba
            la direccion del buffer a partir del argumento */
            if (commands[id].cmd(pid, pid + 1, cmdbuffer + commands[id].len) == 0)
                printf("Error processing command %s, %s\n",
                    commands[id].cmdname, commands[id].cmdhelp);
        }
        else if (id == NOTFOUND)
            printf("bash: %s: command not found\n", cmdbuffer);
        else
            exit = 1;
    }
    return 1;
}

funcId
getCommand(char * cmdbuffer, int bufsize)
{
    funcId id = NOTFOUND;
    int i;
    unsigned char c;
    char * cmdend;

    if (cmdbuffer == NULL || bufsize <= 0 )
        return -1;

    i = 0;

    ERASE_SPACES(c); /* Borra los espacios al comienzo si los hay... */
    /* almacena en el buffer el comando hasta que se presione
    enter o llene el buffer */
    while( c != '\n')
    {
        if (i < bufsize && c != '\t')
        {
            if (c == '\b')
            {
                if (i > 0)
                    cmdbuffer[--i] = '\0';
            }
            else
                cmdbuffer[i++] = c;

            putchar(c);
        }

        c = getchar();

    }
    putchar('\n');

    /* Cuando encuentra el primer espacio (que separa comando de argumentos)
    ** incluye un /0 para mas adelante
    ** distinguir entre comandos y argumentos y ademas poder parsear el comando
     */
    if ( (cmdend = strchr(cmdbuffer, ' ')) != NULL )
        *cmdend = '\0';


    /* compara comando y su finalizacion de \0 */
    id = parseCommand(cmdbuffer, strlen(cmdbuffer) + 1);

    /* si se apreta directamente enter, al no ser un comando invalido,
    se tiene que ejecutar la funcion "nothing".
    por ende en la comparacion contra la lista de comandos se tiene
    que comparar el '\0' y asignar id 0 como respuesta */
    if (i == 0)
        return 0;

    return id;
}


funcId
parseCommand(char * cmdbuffer, unsigned int length)
{
    int i, found = 0;

    /* Barre la lista de comandos posibles comparando que coincidan
    pero ademas coincidan en longitud, pues podria ocurrir que se
    escriba helplalal y lo tomaria como comando help */
    for (i = 0 ; i < sizeof(commands) / sizeof(commands[0]) && !found; i++)
        if (strncmp(cmdbuffer, commands[i].cmdname, commands[i].len) == 0 &&
            length == commands[i].len)
                    found = 1;

    if(found)
        return ( (funcId) (i-1) );

    return NOTFOUND;
}


void
prompt(int shellnum)
{
    printf("user@shell%d:$ ", shellnum);
    return;
}

static void
clearBuffer(char * buffer, int bufsize)
{
    int i;

    if (buffer == NULL || bufsize <= 0)
        return;

    for (i = 0 ; i < bufsize ; i++)
        buffer[i] = '\0';

    return;
}


static int
handlerExit(int ppid, int pid, const char * args)
{
    clearScreen();
    return EXIT;
}

static int
handlerTop(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("top", top, CurrentTTY, 0, NULL, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("top", top, CurrentTTY, 0, NULL, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handlerRestart(int ppid, int pid, const char * args)
{
    restart();
    return 1;
}

static int
handlerShutdown(int ppid, int pid, const char * args)
{
    initAcpi();
    acpiPowerOff();

    return 1;
}

static int
handlerNop(int ppid, int pid, const char * args)
{
    return 1;
}

static int
handlerHelp(int ppid, int pid, const char * args)
{
    int i, found = 0;

    if (args == NULL || *args == '\0')
        return 0;

    for (i = 0 ; i < sizeof(commands) / sizeof(commands[0]) && !found; i++)
        if (strncmp(args, commands[i].cmdname, commands[i].len) == 0)
                    found = 1;

    if ( found )
        printf("%s\n", commands[i-1].cmdhelp);
    else
        printf("no help available for command %s\n", args);

    return found;
}

static int
handlerLs(int ppid, int pid, const char * args)
{
   int i;

   for (i=0; i < CMD_LIST_LEN ; i++)
   {
       if (commands[i].cmdname != "")
       printf("%s\t", commands[i].cmdname);
   }

    putchar('\n');

   return 1;
}

static int
handlerCounter(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("Counter", counter, CurrentTTY, 1, (char **)1, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("Counter", counter, CurrentTTY, 1, (char **)1, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handlerClear(int ppid, int pid, const char * args)
{
    clearScreen();
    return 1;
}

static int
handlerKeybLayout(int ppid, int pid, const char * args)
{
    int rta = 1;

    if ( strcmp(args, "spanish") == 0 )
    {
        set_key_map(ES_KEYB);
        printf("bash: Layout set, ES\n");
    }
    else if ( strcmp(args, "english") == 0 )
    {
        set_key_map(US_KEYB);
        printf("bash: Layout set, EN\n");
    }
    else
        rta = 0;

    return rta;
}

static int
handlerSched(int ppid, int pid, const char * args)
{
    int rta = 1;

    if ( strcmp(args, "lottery") == 0 )
    {
        setSched(SCHED_LOTTERY);
        printf("Set scheduler alg to lottery\n");
    }
    else if ( strcmp(args, "priority") == 0 )
    {
        setSched(SCHED_PRIORITY);
        printf("Set scheduler alg to priority\n");
    }
    else
        rta = 0;

    return rta;
}

static int
handlerSkin(int ppid, int pid, const char * args)
{
    int rta = 1;

    if ( strcmp(args, "matrix") == 0 )
        changeColors(BLACK, GREEN);
    else if ( strcmp(args, "pinkdance") == 0 )
        changeColors(LMAGENTA, BLACK);
    else if ( strcmp(args, "default") == 0 )
        changeColors(BLACK, LGREY);
    else
        rta = 0;

    if (rta > 0)
    {
        clearScreen();
        printf("Skin Loaded...\n");
    }

    return rta;
}

static int
handlerNice(int ppid, int pid, const char * args)
{
    int rta = 1;
    int pid1 = 0;
    int niceVal = 0;
    char c[2];

    if ( strlen(args) == 0 )
        return 0;

    if ( (pid1 = atoi(args)) != 0 )
    {
        printf("Please choose a priority between 0-4: ");
        c[0] = getchar();
        c[1] = '\0';
        niceVal = atoi(c);

        if (niceVal < 0 || niceVal > 4)
            return 0;

        nice(pid1, niceVal);
    }
    else
        rta = 0;

    return rta;
}

static int
handlerKill(int ppid, int pid, const char * args)
{
    int rta = 1;
    int pid2kill = 0;

    if ( strlen(args) == 0 )
        return 0;

    if ( (pid2kill = atoi(args)) != 0 )
        Destroy(pid2kill);
    else
        rta = 0;

    return rta;
}

static int
handlerLspci(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("lspci", lspci, CurrentTTY, 0, NULL, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("lspci", lspci, CurrentTTY, 0, NULL, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handlerDate(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("Date", date, CurrentTTY, 0, NULL, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("Date", date, CurrentTTY, 0, NULL, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handler2Ciclo(int ppid, int pid, const char * args)
{
    CreateProcessAt("Ciclo_priority0", ciclo, CurrentTTY, 0, NULL, 0x400, 0, 0);
    CreateProcessAt("Ciclo_priority4", ciclo, CurrentTTY, 0, NULL, 0x400, 4, 0);
    _Sti();
    _int8();
    
    return 1;
}

static int
handlerCiclo(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("Ciclo", ciclo, CurrentTTY, 0, NULL, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("Ciclo", ciclo, CurrentTTY, 0, NULL, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handler2Hola(int ppid, int pid, const char * args)
{
    CreateProcessAt("Hola_priority0", hola, CurrentTTY, 0, NULL, 0x400, 0, 0);
    CreateProcessAt("Hola_priority4", hola, CurrentTTY, 0, NULL, 0x400, 4, 0);
    _Sti();
    _int8();
    
    return 1;
}

static int
handlerHola(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("Hola", hola, CurrentTTY, 0, NULL, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("Hola", hola, CurrentTTY, 0, NULL, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handlerRandom(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("random", random, CurrentTTY, 1, (char **)99, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("random", random, CurrentTTY, 1, (char **)99, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handlerFactOK(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("fact", factOK, CurrentTTY, 1, (char **)10, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("fact", factOK, CurrentTTY, 1, (char **)10, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

static int
handlerFactNotOK(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("fact", factNotOK, CurrentTTY, 1, (char **)10, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("fact", factNotOK, CurrentTTY, 1, (char **)10, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}


static int
handlerLsusb(int ppid, int pid, const char * args)
{
    int rta = 1;
    if ( strlen(args) == 0 )
        CreateProcessAt("lsusb", lsusb, CurrentTTY, 0, NULL, 0x400, 2, 1);
    else if( strcmp(args, "&") == 0 )
        CreateProcessAt("lsusb", lsusb, CurrentTTY, 0, NULL, 0x400, 2, 0);
    else
        rta = 0;

    _Sti();
    _int8();

    return rta;
}

